package maMetrics;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import maConfigration.MetricSettings;
import maUtil.LogFile;
import maUtil.RecordUnit;
import maUtil.SortedRecords;

import com.telelogic.rhapsody.core.IRPAttribute;
import com.telelogic.rhapsody.core.IRPClass;
import com.telelogic.rhapsody.core.IRPInterfaceItem;
import com.telelogic.rhapsody.core.IRPOperation;
import com.telelogic.rhapsody.core.IRPPackage;
import com.telelogic.rhapsody.core.IRPProject;

public class MetricCC extends MetricObject{
	
	protected static List<String> _allCCKeywords = 
			Arrays.asList("if", "else", "for", "switch", "while");
	 
	protected static int MAXRECORDNUMBER = 100;
	
	
	
	protected boolean _isCollectPkgCls = false;
	protected boolean _isCollectProjCls = false;
	protected boolean _isCollectProjPkg = false;
	protected boolean _isCollectProjOP = false;
	protected boolean _isCollectLogPkgOp = false;
	protected boolean _isCollectLogClsOp = false;
	protected boolean _isCollectOverall = false;
	
	protected SortedRecords _srProjOp = null;
	protected SortedRecords _srPkgOp = null;
	protected SortedRecords _srClsOp = null;
	
	protected SortedRecords _srPkgCls = null;
	protected SortedRecords _srProjCls = null;
	protected SortedRecords _srProjPkg = null;
	
	protected SortedRecords _srOverall = null;
	
	protected LogFile _logProjOp = null;
	//protected LogFile _logClsOp = null;
	//protected LogFile _logPkgOp = null;
	//protected LogFile _logPkgCls = null;
	protected LogFile _logProjCls = null;
	//protected LogFile _logProjPkg = null;
	protected LogFile _logTop = null;
	protected LogFile _logRawData = null;
	
	
	protected int _nSumCls = 0;
	protected int _nSumPkg = 0;
	protected int _nSumProj = 0;
	
	protected int _nRecordBar = 0;
	protected int _nListLength = 0;
	
	protected String _sProjName = null;
	protected String _sPkgName = null;
	protected String _sClsName = null;

	public MetricCC(MetricSettings ms) {
		super(ms);
		
		//skip some log files to save space
		_isCollectProjCls = true;
		//_isCollectPkgCls = true; 
		_isCollectProjOP = true;
		//_isCollectLogPkgOp = true;
		//_isCollectLogClsOp = true;
		//_isCollectProjPkg = true;
		_isCollectOverall = true;
		
		_sMetricName = "Metric: Cyclomatic Complexity";
		
		_nRecordBar = ms.getTopListBar();
		_nListLength = 20;
		
		//_logClsOp = new LogFile("MetricCCLogOpInCls.txt");
		//_logPkgOp = new LogFile("MetricCCLogOpInPkg.txt");
		_logProjOp = new LogFile("MetricCCLogOpInProj.txt");
		
		//_logPkgCls = new LogFile("MetricCCLogClsInPkg.txt");
		_logProjCls = new LogFile("MetricCCLogClsInProj.txt");
		//_logProjPkg = new LogFile("MetricCCLogPkgInProj.txt");
		_logTop = new LogFile("MetricCCLogTop.txt");
		if(_isLogRawData)
		{
			_logRawData = new LogFile("MetricCCRawData.csv");
			_logRawData.logLn("Value,Classname");
		}
		_srProjOp = new SortedRecords(_nListLength, null);
		_srPkgOp = new SortedRecords(_nListLength, null);
		_srClsOp = new SortedRecords(_nListLength, null);
		
		_srPkgCls = new SortedRecords(_nListLength, null);
		_srProjCls = new SortedRecords(_nListLength, null);
		_srProjPkg = new SortedRecords(_nListLength, null);
		
		_srOverall = new SortedRecords(MAXRECORDNUMBER, null);

	}

	@Override
	public int applyInProj(IRPProject proj) {
		if(!_isApplyToProj)
			return 0;
		try{
			_sProjName = proj.getName();
		}
		catch(Exception e)
		{
			System.out.println("Failed to get project name.");
			_sProjName = null;
		}
		
		
		int n = _nSumProj;
		_nSumProj = 0;
		if(_isCollectProjOP)
			logProjOp();
		if(_isCollectProjPkg)
		{//if collect top package list in project
			logProjPkg();
		}
		if(_isCollectProjCls)
		{//if collect top classes list in project
			logProjCls();
		}
		if(_isCollectOverall)
		{//if collect operation list higher than record bar
			logOverall();
		}
		
		//_sPreProj = proj.getName();
		return n;
	}


	protected void logProjOp() {
		// TODO Auto-generated method stub
		if(_srProjOp.getSize() > 0)
		{				
			if(_srProjOp.getName()==null) 
				_srProjOp.setName(_sMetricName + ":Top " + 
					_nListLength + " operations in project " + _sProjName);
			_logProjOp.log(_srProjOp.getAllString());
		}
		_srProjOp = new SortedRecords(_nListLength, null);
	}



	protected void logOverall() {
		// TODO Auto-generated method stub
		if(_srOverall.getSize() > 0)
		{
			if(_srOverall.getName() == null)
			{
				_srOverall.setName(_sMetricName + 
						":All operations in project " + _sProjName+ " higher than threshold.");
			}
			_logTop.log(_srOverall.getAllString());
		}
		_srOverall = new SortedRecords(MAXRECORDNUMBER, null);
	}



	protected void logProjCls() {
		// TODO Auto-generated method stub
		if(_srProjCls.getSize() > 0)
		{
			if(_srProjCls.getName() == null)
			{
				_srProjCls.setName(_sMetricName + ":Top " + 
						_nListLength + " classes in project " + _sProjName);
			}
			_logProjCls.log(_srProjCls.getAllString());
		}
		_srProjCls = new SortedRecords(_nListLength, null);
	}



	protected void logProjPkg() {
		// TODO Auto-generated method stub
		if(_srProjPkg.getSize() > 0)
		{
			if(_srProjPkg.getName() == null)
			{
				_srProjPkg.setName(_sMetricName + ":Top " + 
						_nListLength + " packages in project " + _sProjName);
			}
			//_logProjPkg.log(_srProjPkg.getAllString());
		}
		_srProjPkg = new SortedRecords(_nListLength, null);
	}



	@Override
	public int applyInPkg(IRPPackage pkg) {
		if(!_isApplyToPkg)
			return 0;
		_sPkgName = pkg.getFullPathNameIn();
		if(_isCollectLogPkgOp) logPkgOp();//log PkgOp
		
		int n = _nSumPkg;
		if(_isCollectProjPkg && (_srProjPkg.notFull() || n > _srProjPkg.getMinValue()))
		{//if collect top package list in a project and need to add to list
			RecordUnit ru = new RecordUnit(n, pkg.getFullPathNameIn());
			_srProjPkg.addIfGTZero(ru);
		}
		
		_nSumProj += n; //add to project sum
		if(_isCollectPkgCls)
		{//if collected top class in package
			logPkgCls();
		}
		
		_nSumPkg = 0;
		
		return n;
	}

	protected void logPkgCls() {
		// TODO Auto-generated method stub
		if(_srPkgCls.getSize() > 0)
		{
			if(_srPkgCls.getName() == null)
			{
				_srPkgCls.setName(_sMetricName + ":Top " + _nListLength + " classes in package " + 
						_sPkgName);
			}
			//_logPkgCls.log(_srPkgCls.getAllString());//log old data if any
		}
		_srPkgCls = new SortedRecords(_nListLength, null);
	}



	protected void logPkgOp() {
		// TODO Auto-generated method stub
		if(_srPkgOp.getSize() > 0)
		{
			if(_srPkgOp.getName() == null)
			{
				_srPkgOp.setName(_sMetricName + ": Top " + _nListLength + " operations in package " + 
						_sPkgName);
			}
			//_logPkgOp.log(_srPkgOp.getAllString());
		}
		_srPkgOp = new SortedRecords(_nListLength, null);
	}



	@Override
	public int applyInCls(IRPClass cls) {
		if(!_isApplyToCls)
			return 0;
		_sClsName = cls.getFullPathNameIn();
		if(_isCollectLogClsOp) logClsOp();
		
		_nSumPkg += _nSumCls;//add to package sum
		int n = _nSumCls;
		
		if(_isCollectPkgCls && (_srPkgCls.notFull() || n > _srPkgCls.getMinValue()))
		{//if collect class list in package and need to add into list
			RecordUnit ru = new RecordUnit(n, cls.getFullPathNameIn());
			_srPkgCls.addIfGTZero(ru);
		}
		if(_isCollectProjCls && (_srProjCls.notFull() || n > _srProjCls.getMinValue()))
		{//if collect class list in project and need to add into list
			RecordUnit ru = new RecordUnit(n, cls.getFullPathNameIn());
			_srProjCls.addIfGTZero(ru);
		}
		
		if(_isLogRawData && n>0) 
			_logRawData.logLn(n + "," + cls.getFullPathNameIn());
		
		_nSumCls = 0;
		return n;
	}

	protected void logClsOp() {
		// TODO Auto-generated method stub
		if(_srClsOp.getSize() > 0)
		{//log if the list is not empty
			if(_srClsOp.getName() == null)
			{
				_srClsOp.setName(_sMetricName + ": Top " + _nListLength +
						 " operations in cls " + _sClsName);
			}
			//_logClsOp.log(_srClsOp.getAllString());
		}
		_srClsOp = new SortedRecords(_nListLength, null);
	}



	@Override
	public int applyInItf(IRPInterfaceItem itf) {
		return 0;
	}

	@Override
	public int applyInOp(IRPOperation op) {
		String str = null;
		try{
			str = op.getBody();
		}
		catch(Exception e)
		{
			return 0;
		}
		String name = op.getName();
//		if(name.contains("readDddid"))
//			System.out.print("bablabal");
		int n = countCC(str);
		_nSumCls += n; //add to class sum
		RecordUnit ru = new RecordUnit(n, op.getFullPathNameIn());
		if(n>0)
		{
			if(n > _nRecordBar)
				_srOverall.addWithoutSort(ru);//add to the list which is higher than record bar
			if(_isApplyToCls && (_srClsOp.notFull() || n > _srClsOp.getMinValue())) 
			{
				_srClsOp.add(ru);
			}
			if(_isApplyToPkg && (_srPkgOp.notFull() || n > _srPkgOp.getMinValue())) 
			{
				_srPkgOp.add(ru);
			}
			if(_isApplyToProj && (_srProjOp.notFull() || n > _srProjOp.getMinValue())) 
			{
				_srProjOp.add(ru);
			}
		}
		
		return n;
	}

	@Override
	public int applyInAttr(IRPAttribute attrb) {
		return 0;
	}
	
	public static int countCC(String str)
	{
		int cc = 0;
		str = removeComments(str);
		str = str.replaceAll("else if", "if");
		for(int i=0; i<_allCCKeywords.size(); ++i)
		{
			cc += countKeyworkds(str, _allCCKeywords.get(i));
		}
		cc += findAndOr(str);
		return cc;
	}
	

	
	protected static int countKeyworkds(String str, String subStr) 
	{
		int n = 0;
		if(str.isEmpty() || subStr.isEmpty() || str.length()<subStr.length())
		{//return 0 if
			return 0;
		}
		
		int i = str.indexOf(subStr);
		while(i > 0)
		{
			if(i > 0 && (i+subStr.length() < str.length()))
			{//if the keyword found in the middle of the code
				if(!isNormalChar(str.charAt(i-1)) && !isNormalChar(str.charAt(i+subStr.length())))
					n++;//count if the keyword is not surrounded by normal chars
			}
			else if(i == 0)
			{//if the keyword found in the beginning of the code
				if(!isNormalChar(str.charAt(i+subStr.length())))
				n++;//count if the keyword is not followed by a normal char
			}
			else //ignore other cases
			{}
			i = str.indexOf(subStr, i+subStr.length());
		}
		
		return n;
	}
	
	//return true if it is a normal char
	protected static boolean isNormalChar(char a)
	{
		boolean ret = false;
		if((a>'a' && a<'z')||(a>'A' && a<'Z')||(a == '_')||(a>'0' && a<'9'))
			ret = true;
		return ret;
	}
	
	//return true if it is a normal char or bracket
	protected static boolean isNormalCharAndBracket(char a)
	{
		boolean ret = false;
		if(isNormalChar(a) || a == '{' || a == '(' || a == ')' || a == '}')
			ret = true;
		return ret;
	}
	
	//count && and || in the code 
	protected static int findAndOr(String str)
	{
		int n = 0;
		int i = str.indexOf("&&");
		while(i>0)
		{
			n++;
			i = str.indexOf("&&", i + 2);
		}
		i = str.indexOf("||");
		while(i>0)
		{
			n++;
			i = str.indexOf("||", i + 2);
		}
		return n;
	}
	/**
	 * Remove comments in the code
	 * 
	 * */
	public static String removeComments(String body) {
		
		int n = body.indexOf("//");
		while(n >= 0)
		{// remove part after "//" and before "\n"
			String[] str = body.split("//", 2);
			body = str[0];
			str = str[1].split("\\\n", 2); 
			if(str.length > 1)
				body += str[1];
			//do nothing if the length is 1 or 0, 
			//because the part is commented out anyway
			n = body.indexOf("//");
		}
		n = body.indexOf("/*");
		while(n > 0)
		{// remove part after "/*" and before "*/"
			String[] str = body.split("/\\*", 2);
			body = str[0];
			str = str[1].split("\\*/", 2);
			if(str.length > 1)
				body += str[1];
			//do nothing if the length is 1 or 0, 
			//because the part is commented out anyway
			n = body.indexOf("/*");
		}
		return body;
	}



	@Override
	public void finish() {
		logProjOp();
		logPkgOp();
		logClsOp();
		logPkgCls();
		logProjCls();
		logProjPkg();
		logOverall();
		
	}



}
